#include "DataFrame.h"
#include "NDimsArray.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdexcept>

using namespace darr;

namespace EnvData {

DataFrame::DataFrame() : is_initialized_(false) {
    dimension_names_ = {"time", "latitude", "longitude"};
}

void DataFrame::initialize(const std::vector<double>& time_points,
                          const std::vector<double>& latitudes,
                          const std::vector<double>& longitudes) {
    if (time_points.empty() || latitudes.empty() || longitudes.empty()) {
        throw std::invalid_argument("data can not be empty");
    }
    
    clear();
    
    dimension_scales_.clear();
    dimension_scales_.push_back(time_points);
    dimension_scales_.push_back(latitudes);
    dimension_scales_.push_back(longitudes);
    
    is_initialized_ = true;
    
    std::cout << "DataFrame Initialize Finished: " 
              << time_points.size() << " time points, "
              << latitudes.size() << " latitudes, "
              << longitudes.size() << " longitudes" << std::endl;
}

void DataFrame::addData(DataType data_type, const std::vector<double>& data) {
    if (!is_initialized_) {
        throw std::runtime_error("DataFrame has not Initialized");
    }
    
    size_t expected_size = dimension_scales_[0].size() * 
                          dimension_scales_[1].size() * 
                          dimension_scales_[2].size();
    
    if (data.size() != expected_size) {
        throw std::invalid_argument("size not match");
    }
    
    std::vector<double> copy_data = data;
    auto array = std::make_shared<DimsArray<double>>(dimension_names_, dimension_scales_, copy_data);
    data_arrays_[data_type] = std::move(array);
}

void DataFrame::setNestedData(DataType data_type, 
                             const std::vector<std::vector<std::vector<double>>>& nested_data) {
    if (!is_initialized_) {
        throw std::runtime_error("DataFrame has not Initialized");
    }
    
    if (nested_data.size() != dimension_scales_[0].size()) {
        throw std::invalid_argument("The Sizes of Time Points are not Match");
    }
    
    for (size_t t = 0; t < nested_data.size(); ++t) {
        if (nested_data[t].size() != dimension_scales_[1].size()) {
            throw std::invalid_argument("The Sizes of Latitudes are not Match");
        }
        for (size_t lat = 0; lat < nested_data[t].size(); ++lat) {
            if (nested_data[t][lat].size() != dimension_scales_[2].size()) {
                throw std::invalid_argument("The Sizes of Longitudes are not Match");
            }
        }
    }
    
    auto array = std::make_shared<DimsArray<double>>(dimension_names_, dimension_scales_);
    array->setDataNested(nested_data);
    data_arrays_[data_type] = std::move(array);    
    std::cout << "Set DataType to String" << dataTypeToString(data_type) << std::endl;
}

double DataFrame::getValue(DataType data_type, const SpatioTemporalCoord& coord) const {
    if (!hasDataType(data_type)) {
        throw std::runtime_error("data type not exist" + dataTypeToString(data_type));
    }
    
    if (!isValidCoordinate(coord)) {
        throw std::invalid_argument("axis out of range");
    }
    
    std::map<std::string, double> coord_map = {
        {"time", coord.time},
        {"latitude", coord.latitude},
        {"longitude", coord.longitude}
    };
    return data_arrays_.at(data_type)->at(coord_map);
}

std::vector<double> DataFrame::getRegionData(DataType data_type,
                                           const std::pair<double, double>& time_range,
                                           const std::pair<double, double>& lat_range,
                                           const std::pair<double, double>& lon_range) const {
    if (!hasDataType(data_type)) {
        throw std::runtime_error("data type not exist" + dataTypeToString(data_type));
    }
    
    std::vector<std::pair<double, double>> ranges = {time_range, lat_range, lon_range};
    std::vector<std::vector<double>> selected_scales;
    
    return data_arrays_.at(data_type)->regionScale(ranges, selected_scales);
}

std::vector<double> DataFrame::getTimeSeries(DataType data_type, double latitude, double longitude) const {
    if (!hasDataType(data_type)) {
        throw std::runtime_error("data type not exist" + dataTypeToString(data_type));
    }
    
    std::vector<double> time_series;
    time_series.reserve(dimension_scales_[0].size());
    
    for (double time : dimension_scales_[0]) {
        SpatioTemporalCoord coord(latitude, longitude, time);
        time_series.push_back(getValue(data_type, coord));
    }
    
    return time_series;
}

std::vector<std::vector<double>> DataFrame::getSpatialDistribution(DataType data_type, double time) const {
    if (!hasDataType(data_type)) {
        throw std::runtime_error("data type not exist" + dataTypeToString(data_type));
    }
    
    std::vector<std::vector<double>> spatial_data;
    spatial_data.reserve(dimension_scales_[1].size());
    
    for (size_t lat_idx = 0; lat_idx < dimension_scales_[1].size(); ++lat_idx) {
        std::vector<double> lon_data;
        lon_data.reserve(dimension_scales_[2].size());
        
        for (size_t lon_idx = 0; lon_idx < dimension_scales_[2].size(); ++lon_idx) {
            SpatioTemporalCoord coord(dimension_scales_[1][lat_idx], 
                                    dimension_scales_[2][lon_idx], 
                                    time);
            lon_data.push_back(getValue(data_type, coord));
        }
        spatial_data.push_back(std::move(lon_data));
    }
    
    return spatial_data;
}

bool DataFrame::hasDataType(DataType data_type) const {
    return data_arrays_.find(data_type) != data_arrays_.end();
}

std::vector<size_t> DataFrame::getDimensionSizes() const {
    if (!is_initialized_) {
        return {};
    }
    
    return {dimension_scales_[0].size(), 
            dimension_scales_[1].size(), 
            dimension_scales_[2].size()};
}

std::pair<double, double> DataFrame::getTimeRange() const {
    if (!is_initialized_ || dimension_scales_[0].empty()) {
        throw std::runtime_error("data is not inited");
    }
    
    auto& time_scale = dimension_scales_[0];
    return {*std::min_element(time_scale.begin(), time_scale.end()),
            *std::max_element(time_scale.begin(), time_scale.end())};
}

std::pair<double, double> DataFrame::getLatitudeRange() const {
    if (!is_initialized_ || dimension_scales_[1].empty()) {
        throw std::runtime_error("lat not init");
    }
    
    auto& lat_scale = dimension_scales_[1];
    return {*std::min_element(lat_scale.begin(), lat_scale.end()),
            *std::max_element(lat_scale.begin(), lat_scale.end())};
}

std::pair<double, double> DataFrame::getLongitudeRange() const {
    if (!is_initialized_ || dimension_scales_[2].empty()) {
        throw std::runtime_error("lon data not init");
    }
    
    auto& lon_scale = dimension_scales_[2];
    return {*std::min_element(lon_scale.begin(), lon_scale.end()),
            *std::max_element(lon_scale.begin(), lon_scale.end())};
}

void DataFrame::clear() {
    data_arrays_.clear();
    dimension_scales_.clear();
    is_initialized_ = false;
}

bool DataFrame::loadFromFile(const std::string& filename, DataType data_type) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        std::cerr << "cannot open file" << filename << std::endl;
        return false;
    }
    
    std::vector<double> data;
    std::string line;
    
    while (std::getline(file, line)) {
        std::stringstream ss(line);
        std::string value;
        
        while (std::getline(ss, value, ',')) {
            try {
                data.push_back(std::stod(value));
            } catch (const std::exception& e) {
                std::cerr << "data parser error" << e.what() << std::endl;
                return false;
            }
        }
    }
    
    try {
        addData(data_type, data);
        std::cout << "load data success from" << filename << std::endl;
        return true;
    } catch (const std::exception& e) {
        std::cerr << "add data fail" << e.what() << std::endl;
        return false;
    }
}

bool DataFrame::saveToFile(const std::string& filename, DataType data_type) const {
    if (!hasDataType(data_type)) {
        std::cerr << "data type not exist" << dataTypeToString(data_type) << std::endl;
        return false;
    }
    
    std::ofstream file(filename);
    if (!file.is_open()) {
        std::cerr << "can new file" << filename << std::endl;
        return false;
    }
    
    file << "time,latitude,longitude," << dataTypeToString(data_type) << std::endl;
    
    for (double time : dimension_scales_[0]) {
        for (double lat : dimension_scales_[1]) {
            for (double lon : dimension_scales_[2]) {
                SpatioTemporalCoord coord(lat, lon, time);
                double value = getValue(data_type, coord);
                file << time << "," << lat << "," << lon << "," << value << std::endl;
            }
        }
    }
    
    std::cout << "data export success" << filename << std::endl;
    return true;
}

std::string DataFrame::dataTypeToString(DataType data_type) const {
    switch (data_type) {
        case DataType::WIND_SPEED: return "wind_speed";
        case DataType::WIND_DIRECTION: return "wind_direction";
        case DataType::RAIN_RATE: return "rain_rate";
        case DataType::TEMPERATURE: return "temperature";
        case DataType::PRESSURE: return "pressure";
        case DataType::HUMIDITY: return "humidity";
        default: return "unknown";
    }
}

bool DataFrame::isValidCoordinate(const SpatioTemporalCoord& coord) const {
    if (!is_initialized_) {
        return false;
    }
    
    auto time_range = getTimeRange();
    auto lat_range = getLatitudeRange();
    auto lon_range = getLongitudeRange();
    
    return (coord.time >= time_range.first && coord.time <= time_range.second &&
            coord.latitude >= lat_range.first && coord.latitude <= lat_range.second &&
            coord.longitude >= lon_range.first && coord.longitude <= lon_range.second);
}

std::unique_ptr<EnvironmentalDataManager> EnvironmentalDataManager::instance_ = nullptr;

EnvironmentalDataManager::EnvironmentalDataManager() {
    wind_data_ = std::make_unique<DataFrame>();
    rain_data_ = std::make_unique<DataFrame>();
    weather_data_ = std::make_unique<DataFrame>();
}

EnvironmentalDataManager& EnvironmentalDataManager::getInstance() {
    if (instance_ == nullptr) {
        instance_ = std::unique_ptr<EnvironmentalDataManager>(new EnvironmentalDataManager());
    }
    return *instance_;
}

void EnvironmentalDataManager::initializeWindData(const std::vector<double>& time_points,
                                                 const std::vector<double>& latitudes,
                                                 const std::vector<double>& longitudes) {
    wind_data_->initialize(time_points, latitudes, longitudes);
}

void EnvironmentalDataManager::initializeRainData(const std::vector<double>& time_points,
                                                 const std::vector<double>& latitudes,
                                                 const std::vector<double>& longitudes) {
    rain_data_->initialize(time_points, latitudes, longitudes);
}

bool EnvironmentalDataManager::loadAllData(const std::string& config_file) {
    std::ifstream file(config_file);
    if (!file.is_open()) {
        std::cerr << "cannot open cfg file" << config_file << std::endl;
        return false;
    }
    
    std::string line;
    while (std::getline(file, line)) {
        std::stringstream ss(line);
        std::string data_type, filename;
        
        if (std::getline(ss, data_type, '=') && std::getline(ss, filename)) {
            if (data_type == "wind_speed") {
                wind_data_->loadFromFile(filename, DataType::WIND_SPEED);
            } else if (data_type == "rain_rate") {
                rain_data_->loadFromFile(filename, DataType::RAIN_RATE);
            }
        }
    }
    
    return true;
}

void EnvironmentalDataManager::clearAllData() {
    wind_data_->clear();
    rain_data_->clear();
    weather_data_->clear();
}

} // namespace EnvData 